package com.example.currentlimiting.demo.aspect;

import com.example.currentlimiting.demo.annotation.RequestLimit;
import com.example.currentlimiting.demo.exception.AppBusinessException;
import com.example.currentlimiting.demo.utils.IPUtil;
import com.example.currentlimiting.demo.utils.JacksonUtils;
import com.example.currentlimiting.demo.utils.RedisUtils;
import com.example.currentlimiting.demo.utils.WebResult;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Aspect
@Component
@Slf4j
public class RequestLimitAspect {

    @Autowired
    HttpServletRequest request;
    @Resource
    private HttpServletResponse response;
    @Autowired
    RedisUtils redisUtils;

    /**
     * 被此注解标明的方法会被代理
     */
    @Pointcut("@annotation(com.example.currentlimiting.demo.annotation.RequestLimit)")
    public void limitPointCut() {
    }

    /**
     * 环绕通知拿到方法上RequestLimit注解的2个属性值 以作比较
     */
    @Around("limitPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        RequestLimit requestLimit = signature.getMethod().getAnnotation(RequestLimit.class);
        if (requestLimit != null) {
            String key = getCacheKey();
            int limit = requestLimit.limit();
            int time = requestLimit.time();
            limit = limit > 1? (limit - 1):1;
            time = time > 0? time: 1;
            cacheOrRefuse(key, limit, time);
        }
        return point.proceed();
    }

    /**
     * ip+url作为资源的key
     */
    private String getCacheKey() {
        return IPUtil.getIpAddress(request) + ":" + request.getRequestURI();
    }

    /**
     * 达到次数 停止服务 Or 访问次数+1
     */
    private void cacheOrRefuse(String key, int limit, int time) {
        if (redisUtils.hasKey(key)) {
            String count = redisUtils.get(key).get();
            int countRequest = Integer.parseInt(count);
            if (countRequest > limit) {
                throw new AppBusinessException(HttpStatus.FORBIDDEN.getReasonPhrase(), HttpStatus.FORBIDDEN.value(), ">>>超过限制请求数，服务器拒绝了请求");
                //拒绝了请求（服务降级）
//                String limitMsg = JacksonUtils.beanToJson(new WebResult(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase()));
//                log.error(">>>超过限制请求数，服务器拒绝了请求：" + limitMsg);
//                this.outErrorResult(limitMsg);
            } else {
                redisUtils.incr(key, 1);
            }
        } else {
            redisUtils.incrWithExpireTime(key, time);
        }
    }


    /**
     * 将结果返回
     *
     * @param limitMsg
     */
    private void outErrorResult(String limitMsg) {
        response.setContentType("application/json;charset=UTF-8");
        try (ServletOutputStream outputStream = response.getOutputStream()) {
            outputStream.write(limitMsg.getBytes("utf-8"));
        } catch (IOException e) {
            log.error("outErrorResult method IOException", e);
        }
    }


}
